package cn.com.greatwall.flowable.rest;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ValuedDataObject;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.engine.FormService;
import org.flowable.engine.HistoryService;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import cn.com.greatwall.common.utils.SecurityUtils;
import cn.com.greatwall.flowable.common.constant.FlowableConstant;
import cn.com.greatwall.flowable.config.CustomProcessDiagramGenerator;
import cn.com.greatwall.flowable.service.PermissionService;
import cn.com.greatwall.flowable.service.ProcessInstanceService;
import cn.com.greatwall.flowable.service.dto.ProcessInstanceDto;
import cn.com.greatwall.flowable.service.dto.criteria.ProcessInstanceQueryCriteria;
import cn.com.greatwall.logging.annotation.Log;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;

/**
 * @Author herw
 * @Time 2020-11-23 16:47:00
 * @Version 1.0
 * @Description: TODO(用一句话描述该文件做什么)
 */
@RestController
@RequiredArgsConstructor
@Api(tags = "Flowable：流程实例")
@RequestMapping("/api/flowable/instance")
public class ProcessInstanceController {
    private final PermissionService permissionService;
    private final ProcessInstanceService processInstanceService;
    private final FormService formService;
    private final RuntimeService runtimeService;
    private final HistoryService historyService;
    private final RepositoryService repositoryService;
    private final ProcessEngineConfiguration processEngineConfiguration;

    @Log("查询流程实例")
    @ApiOperation("查询流程实例")
    @GetMapping
    @PreAuthorize("@el.check('instance:list')")
    public ResponseEntity<Object> query(ProcessInstanceQueryCriteria criteria, Pageable pageable) {
        return new ResponseEntity<>(processInstanceService.queryAll(criteria, pageable), HttpStatus.OK);
    }

    @Log("查询流程实例")
    @ApiOperation("查询流程实例")
    @GetMapping(value = "/{id}")
    @PreAuthorize("@el.check('instance:list')")
    public ResponseEntity<Object> query(@PathVariable String id) {
        return new ResponseEntity<>(processInstanceService.findById(id), HttpStatus.OK);
    }

    @Log("查询我的流程")
    @ApiOperation("查询我的流程")
    @GetMapping(value = "/involved")
    @PreAuthorize("@el.check('instance:list')")
    public ResponseEntity<Object> queryInvolved(ProcessInstanceQueryCriteria criteria, Pageable pageable) {
        criteria.setInvolvedUser(SecurityUtils.getCurrentUserId().toString());
        return new ResponseEntity<>(processInstanceService.queryAll(criteria, pageable), HttpStatus.OK);
    }

    @Log("启动流程实例")
    @ApiOperation("启动流程实例")
    @PostMapping(value = "/start")
    @PreAuthorize("@el.check('instance:add')")
    public ResponseEntity<Object> start(@Validated @RequestBody ProcessInstanceDto resource) {
        processInstanceService.start(resource);
        return new ResponseEntity<>(HttpStatus.OK);
    }
    
    @Log("挂起流程实例")
    @ApiOperation("挂起流程实例")
    @PutMapping(value = "/suspend")
    @PreAuthorize("@el.check('instance:edit')")
    public ResponseEntity<Object> suspend(@RequestParam String processInstanceId) {
        processInstanceService.suspend(processInstanceId);
        return new ResponseEntity<Object>(HttpStatus.NO_CONTENT);
    }
    
    @Log("激活流程实例")
    @ApiOperation("激活流程实例")
    @PutMapping(value = "/activate")
    @PreAuthorize("@el.check('instance:edit')")
    public ResponseEntity<Object> activate(@RequestParam String processInstanceId) {
        processInstanceService.activate(processInstanceId);
        return new ResponseEntity<Object>(HttpStatus.NO_CONTENT);
    }

    @Log("删除流程实例")
    @ApiOperation("删除流程实例")
    @DeleteMapping
    @PreAuthorize("@el.check('instance:del')")
    public ResponseEntity<Object> delete(@RequestParam String processInstanceId,
            @RequestParam(required = false) boolean cascade, @RequestParam(required = false) String deleteReason) {
        processInstanceService.delete(processInstanceId, cascade, deleteReason);
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @Log("查询过程意见")
    @ApiOperation("查询过程意见")
    @GetMapping(value = "/comments")
    @PreAuthorize("@el.check('comment:list')")
    public ResponseEntity<Object> comments(@RequestParam String processInstanceId) {
        return new ResponseEntity<Object>(processInstanceService.comments(processInstanceId), HttpStatus.OK);
    }

    @Log("查询实例表单")
    @ApiOperation("查询实例表单")
    @GetMapping(value = "/formData")
    @PreAuthorize("@el.check('form:list')")
    public ResponseEntity<Object> formData(@RequestParam String processInstanceId) {
        HistoricProcessInstance processInstance = permissionService.validateReadPermissionOnProcessInstance(
                SecurityUtils.getCurrentUserId().toString(), processInstanceId);
        Object renderedStartForm = formService.getRenderedStartForm(processInstance.getProcessDefinitionId());
        Map<String, Object> variables = null;
        if (processInstance.getEndTime() == null) {
            variables = runtimeService.getVariables(processInstanceId);
        } else {
            List<HistoricVariableInstance> hisVals = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(processInstanceId).list();
            variables = new HashMap<>(16);
            for (HistoricVariableInstance variableInstance : hisVals) {
                variables.put(variableInstance.getVariableName(), variableInstance.getValue());
            }
        }
        Map<String, Object> map = new HashMap<String, Object>(4);
        boolean showBusinessKey = isShowBusinessKey(processInstance.getProcessDefinitionId());
        map.put("showBusinessKey", showBusinessKey);
        map.put(FlowableConstant.BUSINESS_KEY, processInstance.getBusinessKey());
        map.put("renderedStartForm", renderedStartForm);
        map.put("variables", variables);

        return new ResponseEntity<>(map, HttpStatus.OK);
    }

    @Log("查询流程实例图")
    @ApiOperation("查询流程实例图")
    @GetMapping(value = "/image")
    @PreAuthorize("@el.check('instance:edit')")
    public ResponseEntity<Object> image(@RequestParam String processInstanceId) {
        String userId = SecurityUtils.getCurrentUserId().toString();
        HistoricProcessInstance processInstance = permissionService.validateReadPermissionOnProcessInstance(userId,
                processInstanceId);
        ProcessDefinition pde = repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId());
        if (pde == null || !pde.hasGraphicalNotation()) {
            throw new FlowableException("Process instance image is not found with id " + processInstanceId);
        }
        List<String> highLightedFlows = new ArrayList<>();
        List<String> highLightedActivities = new ArrayList<>();
        List<HistoricActivityInstance> allHistoricActivityIntances = historyService
                .createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();
        allHistoricActivityIntances.forEach(historicActivityInstance -> {
            if (BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW.equals(historicActivityInstance.getActivityType())) {
                highLightedFlows.add(historicActivityInstance.getActivityId());
            } else {
                highLightedActivities.add(historicActivityInstance.getActivityId());
            }
        });

        List<String> runningActivitiIdList = null;
        // 流程已结束
        if (processInstance != null && processInstance.getEndTime() != null) {
            runningActivitiIdList = Arrays.asList();
        } else {
            runningActivitiIdList = runtimeService.getActiveActivityIds(processInstanceId);
        }

        BpmnModel bpmnModel = repositoryService.getBpmnModel(pde.getId());
        CustomProcessDiagramGenerator diagramGenerator = (CustomProcessDiagramGenerator) processEngineConfiguration
                .getProcessDiagramGenerator();
        InputStream resource = diagramGenerator.generateCustomDiagram(bpmnModel, "png", highLightedActivities,
                runningActivitiIdList, highLightedFlows, processEngineConfiguration.getActivityFontName(),
                processEngineConfiguration.getLabelFontName(), processEngineConfiguration.getAnnotationFontName(),
                processEngineConfiguration.getClassLoader(), 1.0, true);
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.setContentType(MediaType.IMAGE_PNG);
        try {
            return new ResponseEntity<>(Base64.getEncoder().encodeToString(IOUtils.toByteArray(resource)),
                    responseHeaders, HttpStatus.OK);
        } catch (Exception e) {
            throw new FlowableException("Process instance image read error", e);
        }
    }

    protected boolean isShowBusinessKey(String processDefinitionId) {
        List<ValuedDataObject> dataObjects = repositoryService.getBpmnModel(processDefinitionId).getMainProcess()
                .getDataObjects();
        if (dataObjects != null && dataObjects.size() > 0) {
            for (ValuedDataObject valuedDataObject : dataObjects) {
                if ("showBusinessKey".equals(valuedDataObject.getId())) {
                    if (valuedDataObject.getValue() instanceof String) {
                        return Boolean.valueOf((String) valuedDataObject.getValue());
                    } else if (valuedDataObject.getValue() instanceof Boolean) {
                        return (Boolean) valuedDataObject.getValue();
                    } else {
                        return false;
                    }
                }
            }
        }
        return false;
    }
}
